/*
 * Created on 25.aug.2006
 * Created by Tomas Ekeli
 */
package placing;

import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import people.Person;
import people.Reserver;
import util.Debug;

/**
 * 
 * <p>A Room is a collection of Places. The room is a convenient way of
 * referencing the places, and it includes convenience-methods for acting
 * on the places it contains. A room can contain any number of Places.</p>
 * 
 * @author Tomas
 *
 */
public class Room implements Comparable<Room>, Serializable {
	private static final long serialVersionUID = 1L;
	
	public static final String ELEMENT_NAME = "ROOM";
	private String name;
	private int id;
	private LinkedList<Place> placesInRoom;
	
	/**
	 * Creates an empty room.
	 * @param num The identifying number of the room
	 *
	 */
	public Room(int num) {
		setUp(num, ""+num);
	}
	
	/**
	 * Creates an empty room with a name other than the generated one
	 * @param num The identifying number of the room
	 * @param n The name of the Room.
	 */
	public Room(int num, String n) {
		setUp(num,  n);
	}
	
	/**
	 * If another room has the same identifying number (ie. the same
	 * name) it is the same room. No further sanity-check.
	 */
	public boolean equals(Object o) {
		Room other = (Room)o;
		return (id == other.getID());
	
	}

	public String toString() {
		
		if (Debug.ON) {
			String r = "Room: " + name + "\n";
			if (placesInRoom.isEmpty()) {
				r += "[No Places in Room]\n";
			} else {
				for (Place p : placesInRoom) {
					r += p + "\n";
				}
			}
			return r;
		} else {
			return name;
		}
	}


	/**
	 * Compares the Rooms based on their identifying number.
	 */
	public int compareTo(Room o) {
		return id - o.getID();
	}

	protected boolean isEmpty() {
		return placesInRoom.isEmpty();
	}
	
	/**
	 * Adds a new place to the room, the new place will get the next
	 * number following the current last number.
	 */
	protected void addPlace() {
		if (placesInRoom.isEmpty()) {
			//first place to add
			Place pl = new Place(this, 1);
			placesInRoom.add(pl);
		}
		else {
			//Make sure we take a free place after the last one so far
			sortPlaces();
			int last = placesInRoom.getLast().getPlaceID();
			Place pl = new Place(this, last + 1); 
			placesInRoom.add(pl);
		}
	}
	
	/**
	 * Adds an existing place to the room. The place will keep its number.
	 * Any existing place in the room with the same number will be lost. 
	 * 
	 * @param p The Place to add
	 * @throws PlaceNotFoundException iff the Place was impossible to add (ie.
	 * it had a place-id of 0 or less)
	 */
	public void addPlace(Place p) throws PlaceNotFoundException {
		try {
			if (p.getPlaceID() <= 0) {
				throw new PlaceNotFoundException("Illegal place-number "
						+ p.getPlaceID());
			}
			if (placesInRoom.contains(p)) {
				placesInRoom.remove(p);
			}
			placesInRoom.add(p);
			p.setRoom(this);
		} catch (NullPointerException e) {
			throw new PlaceNotFoundException("Place did not have a number");
		}		

	}
	
	/*
	 * Removes places Places from the Room. The Places removed will be the last
	 * ones in the Room. This method does not care whether the Place is in use.
	 * This is a naught method ;)
	 * 
	 * @param places The number of Places to remove
	 * @throws PlaceNotFoundException iff there are no Places to remove
	 *
	 *
	private void removePlace(int places) throws PlaceNotFoundException {
		if (placesInRoom.isEmpty()) {
			throw new PlaceNotFoundException("Cannot remove a Place, Room is empty");
		}
		//go from the back
		sortPlaces();
		for (int i = 0; i < places; i++) {
			placesInRoom.removeLast();
		}
	} */
	
	/**
	 * Removes the Place from the Room. This method does not care whether the
	 * Place it removes is in use.
	 * 
	 * @param place The number of the Place to remove.
	 * @throws PlaceNotFoundException iff place is not in the Room
	 */
	protected void removePlace(Place place) throws PlaceNotFoundException {
		if (placesInRoom.contains(place)) {
			placesInRoom.remove(place);
		}
		else {
			throw new PlaceNotFoundException("Cannot remove that Place, it is not in this Room");
		}
	}
	
	/**
	 * Removes a free Place from the Room. The free Place removed will be the
	 * one with the highest number.
	 * 
	 * @throws PlaceNotFoundException iff there are no free Places to remove
	 */
	protected void removeFreePlace() throws PlaceNotFoundException {
		if (getNumFreePlaces() <= 0) {
			throw new PlaceNotFoundException("No free Places to Remove");
		}
		reverseSortPlaces();
		placesInRoom.remove(getFreePlace());
		
	}
	
	/**
	 * Removes places free Places from the Room. The free Places removed will be
	 * the ones with the highest numbers.
	 * 
	 * @throws PlaceNotFoundException iff there are no free Places to remove
	 */
	protected void removeFreePlaces(int places) throws PlaceNotFoundException {
		if (getNumFreePlaces() <= 0) {
			throw new PlaceNotFoundException("No free Places to Remove");
		}
		for (int i = 0; i < places; i++) {
			placesInRoom.remove(getFreePlace());
		}
		
	}

	/**
	 * @return The identifying number of this Room
	 */
	public int getID() {
		return id;
	}
	
	public String getName() {
		return name;
	}
	
	protected Place getPlace(int id) throws PlaceNotFoundException {
		for (Place pl : placesInRoom) {
			if (pl.getPlaceID() == id) {
				return pl;
			}
		}
		throw new PlaceNotFoundException("No place with id: " + id);
	}
	
	/**
	 * @return The Places in this Room
	 */
	public List<Place> getPlaces() {
		return new LinkedList<Place>(placesInRoom);
	}
	
	/**
	 * Checks whether the Room contains the Place given
	 * @param pl The Place to look for
	 * @return <code>true</code> iff pl is in the Room.
	 */
	protected boolean contains(Place pl) {
		return placesInRoom.contains(pl);
	}
	
	/**
	 * Searches the Places in the Room, and returns a free one if there are any.
	 * 
	 * @return A free Place in the Room
	 * @throws PlaceNotFoundException iff there are no free Places in the Room
	 */
	protected Place getRandomFreePlace() throws PlaceNotFoundException {
		shufflePlaces();
		return getFreePlace();
	}
	
	/**
	 * Searches the Places in the Room and returns one pers can have, if any.
	 * Returns a random one, if there are several possibilities.
	 * 
	 * @param p The Person to look for a Place to put in
	 * @return A free and valid Place for pers
	 * @throws PlaceNotFoundException iff no Place is available for pers
	 * 
	 */
	protected Place getRandomFreePlace(Person p) throws PlaceNotFoundException {
		shufflePlaces();
		return getFreePlace(p);
	}
	/**
	 * Searches the Places in the Room, and returns a free one if there are any.
	 * 
	 * @return A free Place in the Room
	 * @throws PlaceNotFoundException iff there are no free Places in the Room
	 */
	protected Place getFreePlace() throws PlaceNotFoundException {
		for (Place p : placesInRoom) {
			if (p.isFree()) {
				return p;
			}
		}
		
		throw new PlaceNotFoundException("No free places in " + name);
	}
	
	/**
	 * Returns the number of Places in the Room
	 * @return The total number of Places in the Room.
	 */
	public int getNumPlaces() {
		return placesInRoom.size();
	}

	/**
	 * Searches through the Places in the Room and returns the number of free
	 * ones.
	 * 
	 * @return The count of free Places in the Room
	 */
	public int getNumFreePlaces() {
		int count = 0;
		for (Place p : placesInRoom) {
			if (p.isFree()) {
				count++;
			}
		}
		return count;
	}
	
	/**
	 * Searches the Places in the Room for a free and valid Place for p.
	 * @param p The Person to search for Places for
	 * @return A Place that is available for assignation to the Person
	 * @throws PlaceNotFoundException iff there are no Places in the Room
	 * that are both free and that the Person may have.
	 */
	protected Place getFreePlace(Person p) throws PlaceNotFoundException {
		for (Place place : placesInRoom) {
			if (place.isValidAndFree(p)) {
				return place;
			}
		}
		
		throw new PlaceNotFoundException("No free places in " + name + " for "
				+ p.getName());
	}
	
	/**
	 * Checks to see whether there are free and valid Places for the Person
	 * in the Room
	 * @param p The Person to check for Places to
	 * @return true iff there is at least one free and valid Place for p
	 */
	protected boolean isValid(Person p) {
		try {
			getFreePlace(p);
			return true;
		} catch (PlaceNotFoundException e) {
			return false;
		}
	}
	
	/**
	 * Reserves all the Places in the Room for the Reserver. The Places may be
	 * reserved for more than one Reserver, and these are not exclusive.
	 * 
	 * @param r The Reserver to reserve for.
	 */
	protected void reserveRoom(Reserver r) {
		for (Place p: placesInRoom) {
			p.reserve(r);
		}
	}
	
	/*
	 * Reserves all the Places in the Room for the Person. The Places may be
	 * reserved for more than one Group or Person, and these are not exclusive.
	 * 
	 * @param p The Person to reserve for.
	 *
	protected void reserveRoom(Person p) {
		for (Place place: liPlaces) {
			place.reserve(p);
		}
	}*/
	
	/**
	 * Reserves the specified Place in the Room for the Reserver. The Places may
	 * be reserved for more than one Reserver, and these are not exclusive.
	 * 
	 * @param num The number of the Place to reserve.
	 * @param r The Reserver to reserve for.
	 * @throws PlaceNotFoundException iff there is no place with that number
	 */
	protected void reservePlace(int num, Reserver r) throws PlaceNotFoundException {
		
		try {
			findPlace(num).reserve(r);
		} catch (PlaceNotFoundException e) {
			// No place with that number found
			throw new PlaceNotFoundException("Room " + name 
					+ " Could not reserve place " + num + ", PLACE NOT FOUND");
		}
		
	}
	
	/*
	 * Reserves the specified Place in the Room for the Person. The Places may 
	 * be reserved for more than one Group or Person, and these are not
	 * exclusive.
	 * 
	 * @param num The number of the Place to reserve.
	 * @param p The Person to reserve for.
	 * @throws PlaceNotFoundException iff there is no place with that number
	 *
	protected void reservePlace(int num, Person p) throws PlaceNotFoundException {
		try {
			findPlace(num).reserve(p);
		} catch (PlaceNotFoundException e) {
			// No place with that number found
			throw new PlaceNotFoundException("Room " + sName 
					+ " Could not reserve place " + num + " for " + p.getName() 
					+ ", PLACE NOT FOUND");
		}
	}*/

	/**
	 * Clears all the reservations on all the Places in the Room. After this
	 * method is run any Person can be assigned to any of the Places in the
	 * Room.
	 *
	 */
	protected void clearReservations() {
		for(Place p : placesInRoom) {
			p.clearReservation();
		}
	}

	/**
	 * Clears all the reservations on the specified Place. After this method
	 * is run any Person can be assigned to the Place.
	 * 
	 * @param num The number of the Place to free from reservations
	 * @throws PlaceNotFoundException iff no such Place exists
	 */
	protected void clearReservations(int num) throws PlaceNotFoundException {
		try {
			findPlace(num).clearReservation();
		} catch (Exception e) {
			// No place with that number found
			throw new PlaceNotFoundException("Room " + name 
					+ " Could not free reservations for place " + num 
					+ ", PLACE NOT FOUND");
		}
	}
	
	protected void sortPlaces() {
		Collections.sort(placesInRoom);
	}
	
	private void shufflePlaces() {
		Collections.shuffle(placesInRoom);
	}

	private void reverseSortPlaces() {
		// Sort descending
		PlaceDescendingComparator comp = new PlaceDescendingComparator();
		Collections.sort(placesInRoom, comp);
	}

	/**
	 * Used by the constructors
	 * @param num RoomNumber
	 * @param name RoomName
	 */
	private void setUp(int num, String n) {
		id = num;
		placesInRoom = new LinkedList<Place>();
		name = n;
	}

	/**
	 * Finds the Place specified in the Room, and returns it.
	 * @param num The number of the Place
	 * @return The Place at that number, if any
	 * @throws PlaceNotFoundException iff there is no such Place
	 */
	private Place findPlace(int num) throws PlaceNotFoundException {
		for (Place place : placesInRoom) {
			if (place.getPlaceID() == num) {
				return place;
			}
		}
		// No place with that number found
		throw new PlaceNotFoundException("Room " + name 
				+ " Could not find place " + num);
	}

}
